#-*- coding: utf-8 -*-

# (BSD 2-clause)
# Copyright (c) 2011, Mario Hock, Michael Tänzer, Karin Schmidt.
# All rights reserved.
# 
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE INSTITUTE OF TELEMATICS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ARIBA PROJECT OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation
# are those of the authors and should not be interpreted as representing
# official policies, either expressed or implied, of the copyright holders.


import gtk
import os
import omnibus_steeringwheel as steeringwheel

class User(object):
    '''manages the users UI context'''
    
    current_user = None
    
    # user_area_selector tabs
    USER_AREA_SELECTOR_NO_USER = 0
    USER_AREA_SELECTOR_SOME_USER = 1
    
    # share_selector tabs
    SHARE_SELECTOR_SEND = 0
    SHARE_SELECTOR_OTHERS_SHARES = 1
    SHARE_SELECTOR_MY_SHARES = 2
    
    # others_shares_selector tabs
    OTHERS_SHARES_SELECTOR_NO_CONNECTION = 0
    OTHERS_SHARES_SELECTOR_FETCHING = 1
    OTHERS_SHARES_SELECTOR_LIST = 2
    
    # my_shares_selector tabs
    MY_SHARES_NO_SHARES = 0
    MY_SHARES_SOME_SHARES = 1
    
    # _send_files_list column names
    SEND_NAME = 0
    SEND_SIZE = 1
    SEND_KIND = 2
    SEND_PIC = 3
    SEND_SELECTED = 4
    
    # _others_shares_tree column names
    OTHERS_SHARES_NAME = 0
    OTHERS_SHARES_SIZE = 1
    OTHERS_SHARES_KIND = 2
    OTHERS_SHARES_PIC = 3
    OTHERS_SHARES_DIROBJ = 4
    
    # _my_shares_tree column names
    MY_SHARES_NAME = 0
    MY_SHARES_SIZE = 1
    MY_SHARES_KIND = 2
    MY_SHARES_PIC = 3
    
    
    def __init__(self, user_name, status, builder, connection):
        self._user_name = user_name
        self._status = status
        self._builder = builder
        self._connection = connection
        
        # UI state
        self._server_check_button = False
        self._server_selection = None #TODO: think about whether this should
                                      # actually be global instead of per user
        self._send_files_list = gtk.ListStore(str, str, str, str, 'gboolean')
        self._send_files_list.set_sort_column_id(self.SEND_NAME,
                                                  gtk.SORT_ASCENDING)
        
        self._others_shares_tree = gtk.TreeStore(str, str, str, str, object)
        self._others_shares_tree.set_sort_column_id(self.OTHERS_SHARES_NAME,
                                                  gtk.SORT_ASCENDING)
        
        self._my_shares_tree = gtk.ListStore(str, str, str, str)
        self._my_shares_tree.set_sort_column_id(self.MY_SHARES_NAME,
                                                 gtk.SORT_ASCENDING)
        
        self._others_shares_has_loaded = False
        self._my_shares_has_loaded = False
    
    def repaint(self):
        self._builder.get_object('user_area_label').\
                    set_label(u'<big><b>%s</b></big>' % self._user_name)
        
        connect_action = self._builder.get_object('user_connect_action')
        
        # differentiate between all states
        if self._status == steeringwheel.User.USERSTATUS.DISCONNECTED:
            connect_action.set_stock_id(gtk.STOCK_CONNECT)
            connect_action.set_sensitive(True)
            
        elif self._status == steeringwheel.User.USERSTATUS.CONNECTED:
            connect_action.set_stock_id(gtk.STOCK_DISCONNECT)
            connect_action.set_sensitive(True)

        elif self._status in steeringwheel.User.USERSTATUS.TRANSITIONS:
            connect_action.set_stock_id(gtk.STOCK_DISCONNECT)
            connect_action.set_sensitive(False)
        
#        elif self._status == steeringwheel.User.USERSTATUS.DISCONNECTING:
#            connect_action.set_stock_id(gtk.STOCK_DISCONNECT)
#            connect_action.set_sensitive(False)

        ## connection errors
        elif self._status in steeringwheel.User.USERSTATUS.ERRORS :
            connect_action.set_stock_id(gtk.STOCK_CONNECT)
            connect_action.set_sensitive(True)
        
        else:
            raise steeringwheel.User.USERSTATUS.UnknownConnectionStatusError()
        
        
        connected_user_actions = self._builder.get_object(
                                                    'connected_user_actions')
        send_user_not_connected_error_expander = self._builder.get_object(
                                    'send_user_not_connected_error_expander')
        server_check_button = self._builder.get_object('server_check_button')
        server_selection = self._builder.get_object('server_selection')
        others_shares_selector = self._builder.get_object(
                                                    'others_shares_selector')
        
        ## XXX testing... ignore 'not connected'
        # only care about connected/not connected
        if self._status == steeringwheel.User.USERSTATUS.CONNECTED or True:
            connected_user_actions.set_sensitive(True)
            
            # send tab
            send_user_not_connected_error_expander.set_visible(False)
            
            server_check_button.set_active(self._server_check_button)
            server_selection.set_sensitive(self._server_check_button)
            
            # others shares tab
            others_shares_selector.\
                        set_current_page(self.OTHERS_SHARES_SELECTOR_LIST)
        
        else:
            connected_user_actions.set_sensitive(False)
            
            #send tab
            send_user_not_connected_error_expander.set_visible(True)
            
            server_check_button.set_active(True)
            server_selection.set_sensitive(True)
            
            # others shares tab
            others_shares_selector.\
                    set_current_page(self.OTHERS_SHARES_SELECTOR_NO_CONNECTION)
        
        
        my_shares_selector = self._builder.get_object('my_shares_selector')
        if len(self._my_shares_tree) > 0:
            my_shares_selector.set_current_page(self.MY_SHARES_SOME_SHARES)
        else:
            my_shares_selector.set_current_page(self.MY_SHARES_NO_SHARES)

        
        self._builder.get_object('user_area_selector').\
                    set_current_page(self.USER_AREA_SELECTOR_SOME_USER)
    
    def selected(self):
        '''set up the UI context'''
        server_selection = self._builder.get_object('server_selection')
        # ATTENTION storing the iter only works because it's a ListStore
        if self._server_selection is None:
            server_selection.set_active(-1)
        else:
            server_selection.set_active_iter(self._server_selection)
        
        self._builder.get_object('send_files_tree').\
                        set_model(self._send_files_list)
        
        self._builder.get_object('remote_files_tree').\
                        set_model(self._others_shares_tree)
        
        self._builder.get_object('my_shared_files_tree').\
                        set_model(self._my_shares_tree)
        
        if not self._my_shares_has_loaded:
            self.my_shares_refresh()
            self._my_shares_has_loaded = True
        
        self.repaint()
    
    def deselected(self):
        '''Allow user to save UI context'''
        server_selection = self._builder.get_object('server_selection')
        # ATTENTION storing the iter only works because it's a ListStore
        self._server_selection = server_selection.get_active_iter()
    
    def get_name(self):
        return self._user_name
    
    def get_status(self):
        return self._status
    
    def set_status(self, new_status):
        old_status = self._status
        self._status = new_status
        
        if old_status == new_status:
            if new_status == steeringwheel.User.USERSTATUS.CONNECTING:
                self.still_connecting()
            else:
                return # nothing to see here move along
        
        elif new_status == steeringwheel.User.USERSTATUS.DISCONNECTED:
            if old_status == steeringwheel.User.USERSTATUS.CONNECTING:
                self.connect_aborted()
            else:
                self.disconnected()
        
        elif new_status == steeringwheel.User.USERSTATUS.CONNECTED:
            self.connected()
        
        elif new_status == steeringwheel.User.USERSTATUS.CONNECTING:
            self.connecting()
        
        elif new_status == steeringwheel.User.USERSTATUS.DISCONNECTING:
            self.disconnecting()
            
        elif new_status in steeringwheel.User.USERSTATUS.TRANSITIONS:
            pass
            
        elif new_status in steeringwheel.User.USERSTATUS.ERRORS:
            self.connection_error()
        
        
        else:
            raise steeringwheel.User.USERSTATUS.UnknownConnectionStatusError()
        
        
        if self is User.current_user:
            self.repaint()
            
    def disconnecting(self):
        pass
    
    def disconnected(self):
        pass
    
    def connected(self):
        self._others_shares_has_loaded = False
    
    def connecting(self):
        pass
    
    def still_connecting(self):
        pass
    
    def connect_aborted(self):
        pass

    def connection_error(self):
        pass
    
    def remove(self):
        pass
    
    def _send_add_path(self, path):
        if os.path.isdir(path):
            kind = 'dir'
            stockitem = gtk.STOCK_DIRECTORY
            
            size = 0
            for dir, _, files in os.walk(path):
                for file in files:
                    size += os.path.getsize(os.path.join(dir, file))
        else:
            kind = 'file'
            stockitem = gtk.STOCK_FILE
            size = os.path.getsize(path)
        
        self._send_files_list.append(
                    (path, self._human_filesize(size), kind, stockitem, True) )
    
    def send(self):
        server_selection = self._builder.get_object('server_selection')
        server_iter = server_selection.get_active_iter()
        
        ## XXX testing...
#        if self._status != steeringwheel.User.USERSTATUS.CONNECTED and server_iter is None:
#            self._send_notice_highlight()
#            return
        
        server_check_button = self._builder.get_object('server_check_button')
        server_enabled = server_check_button.get_active()
        
        if server_enabled:
            if server_iter is None:
                # should not happen
                print "Error: Server box checked but no server selected"
                return
            
            from user_list import UserList
            model = server_selection.get_model()
            server = model.get_value(server_iter, UserList.OBJECT_COLUMN)
            
            if server._status != steeringwheel.User.USERSTATUS.CONNECTED:
                #TODO: filter servers so non connected are not even listed
                print "Error: Server %s is not connected" % server.get_name()
                return
            
            command = lambda filename: self._connection.custody(
                                    server.get_name(),
                                    self._user_name,
                                    filename)
        else:
            command = lambda filename: self._connection.push(self._user_name,
                                                             filename)
        
        something_sent = False
        for row in self._send_files_list:
            if row[self.SEND_SELECTED]:
                command(row[self.SEND_NAME])
                something_sent = True
        
        if not something_sent:
            # should not happen
            print "No files sent"
            return
        
        self._send_files_list.clear()
    
    def _send_notice_highlight(self):
        self._builder.get_object('send_user_not_connected_error_expander').\
                                set_expanded(True)
    
    def others_shares_update(self, offers):
        model = self._others_shares_tree
        
        if offers.name == '':
            model.clear()
            insert_point = None
        
        elif offers.parent:
            # get iterator
            node_path = offers.tree_row_ref.get_path()
            if node_path:
                insert_point = model.get_iter(node_path)
            else:
                return
            
            # set new object
            model[insert_point][self.OTHERS_SHARES_DIROBJ] = offers
            model[insert_point][self.OTHERS_SHARES_KIND] = 'dir'
            
            # remove subelements
            it = model.iter_children(insert_point)
            while it:
                model.remove(it)
                it = model.iter_children(insert_point)
        else:
            # no parent directory for this offered dir and not the root?
            # probably a request from another UI -> ignore
            return
        
        self._others_shares_add_all(offers, insert_point, model)
        
        if insert_point:
            self._builder.get_object('remote_files_tree').expand_row(
                                                model.get_path(insert_point),
                                                False)
    
    
    def _others_shares_add_all(self, offers, parent, model):
        for offername in offers:
            offer = offers[offername]
            
            if isinstance(offer, steeringwheel.OfferedFile):
                node = model.append(parent,
                                        (offername,
                                         self._human_filesize(offer.size()),
                                         'file',
                                         gtk.STOCK_FILE,
                                         offer)
                                    )
            elif isinstance(offer, steeringwheel.OfferedDummyDir):
                node = model.append(parent,
                                        (offername,
                                         '[%i]' % offer.size(),
                                         'subdir',
                                         gtk.STOCK_DIRECTORY,
                                         offer)
                                    )
                
                if offer.size() > 0:
                    model.append(node,
                                    ('Placeholder',
                                     '',
                                     'placeholder',
                                     gtk.STOCK_DIALOG_ERROR,
                                     None)
                                 )
                
            elif isinstance(offer, steeringwheel.OfferedConcreteDir):
                node = model.append(parent,
                                        (offername,
                                         '[%i]' % offer.size(),
                                         'dir',
                                         gtk.STOCK_DIRECTORY,
                                         offer)
                                    )
                self._others_shares_add_all(offer, node, model)
            
            offer.tree_row_ref = gtk.TreeRowReference(model, model.get_path(node))
    
    
    def _human_filesize(self, size):
        num = float(size)
        
        for x in ['bytes','KB','MB','GB','TB']:
            if ( num < 1024.0 ):
                return "%3.1f %s" % (num, x)
            num /= 1024.0
    
    def my_shares_refresh(self):
        self._connection.show(self._user_name)
    
    def my_shares_update(self, shares):
        self._my_shares_tree.clear()
        
        for share in shares:
            assert isinstance(share, steeringwheel.Share)
            
            #TODO: make it more verbose
            self._my_shares_tree.append(
                    (share.path, '', '', gtk.STOCK_FILE) )
        
        self.repaint()
    
    #===========================================================================
    # Action Handlers
    #===========================================================================
    def on_user_connect_action_activate(self, action):
        if self._status == steeringwheel.User.USERSTATUS.DISCONNECTED:
            self._connection.connect(self._user_name)
        elif self._status == steeringwheel.User.USERSTATUS.CONNECTED:
            self._connection.disconnect(self._user_name)
    
    def on_server_check_button_toggled(self, button):
        if self._status == steeringwheel.User.USERSTATUS.CONNECTED:
            self._server_check_button = button.get_active()
        else:
            if not button.get_active():
                self._send_notice_highlight()
        self.repaint()
    
    def on_send_file_chooser_open_activate(self):
        files = self._builder.get_object('send_file_chooser_dialog').\
                                    get_filenames()
        for file in files:
            self._send_add_path(file)
            
    def on_send_files_selected_toggled(self, path):
        self._send_files_list[path][self.SEND_SELECTED] = \
            not self._send_files_list[path][self.SEND_SELECTED]
    
    def on_send_send_activate(self):
        self.send()
    
    def on_user_get_shares_list_action_activate(self):
        self._connection.look(self._user_name)
    
    def on_remote_files_tree_row_activated(self, row_path):
        model = self._others_shares_tree
        name = model[row_path][self.OTHERS_SHARES_NAME]
        kind = model[row_path][self.OTHERS_SHARES_KIND]
        obj = model[row_path][self.OTHERS_SHARES_DIROBJ]
        
        if isinstance(obj, steeringwheel.OfferedDir):
            self._builder.get_object('remote_files_tree').expand_row(row_path,
                                                                     False)
        elif kind == 'file':
            self._builder.get_object('others_shares_download').activate()
        else:
            print(u'Nothing to do with %s' % name)
    
    def on_remote_files_tree_row_expanded(self, row):
        dirobj = self._others_shares_tree[row][self.OTHERS_SHARES_DIROBJ]
        
        if isinstance(dirobj, steeringwheel.OfferedDummyDir):
            # remove placeholders
            it = self._others_shares_tree.iter_children(row)
            while it:
                self._others_shares_tree.remove(it)
                it = self._others_shares_tree.iter_children(row)
            
            self._others_shares_tree.append(row,
                                                ('Retrieving contents',
                                                 '',
                                                 'placeholder',
                                                 gtk.STOCK_REFRESH,
                                                 None)
                                            )
            
            dirobj.refresh()
    
    def on_others_shares_download_activate(self):
        tree_view = self._builder.get_object('remote_files_tree')
        model, paths = tree_view.get_selection().get_selected_rows()
        if not paths:
            print('No file selected for download')
        
        for path in paths:
            model[path][self.OTHERS_SHARES_DIROBJ].pull()
    
    def on_my_shares_file_chooser_open_activate(self):
        files = self._builder.get_object('my_shares_file_chooser_dialog').\
                                    get_filenames()
        
        for file in files:
            self._connection.give(self._user_name, file)
        
        self.my_shares_refresh()
    
    def on_users_tree_row_activated(self):
        if self._status == steeringwheel.User.USERSTATUS.DISCONNECTED:
            self._builder.get_object('user_connect_action').activate()
        elif self._status == steeringwheel.User.USERSTATUS.CONNECTED:
            self._builder.get_object('user_chat_action').activate()
    
    def on_share_selector_switch_page(self, page_num):
        if page_num == self.SHARE_SELECTOR_OTHERS_SHARES:
            if not self._others_shares_has_loaded and \
                    self._status == steeringwheel.User.USERSTATUS.CONNECTED:
                self._builder.get_object('user_get_shares_list_action').\
                                                                    activate()
                self._others_shares_has_loaded = True


class DummyUser(object):
    '''Pseudo User to handle the situation when no user is selected'''
    
    def __init__(self, builder):
        self._builder = builder
    
    def selected(self):
        self._builder.get_object('user_area_selector').\
                    set_current_page(User.USER_AREA_SELECTOR_NO_USER)
        self._builder.get_object('user_connect_action').set_sensitive(False)
    
    def deselected(self):
        pass
